/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  [DebuggerDisplay( "ChildrenCount = {ChildrenCount}, Length = {Length}" )]
  internal class ListDataItemIndexGroupContainer : ListDataItemIndexNode
  {
    #region Constructor

    internal ListDataItemIndexGroupContainer(
      ListDataItemIndexConfiguration configuration,
      IEqualityComparer<StatFunction> statFunctionComparer,
      int level, 
      int startIndex,
      int length,
      IEnumerable<ListDataItemIndexGroup> childrenGroups,
      Func<LinkedListNode<object>, int, ListDataItemIndexNode> containerCreator )
      : base( configuration, statFunctionComparer, level, startIndex, length )
    {
      if( childrenGroups == null )
        throw new ArgumentNullException( "childrenGroups" );

      if( !childrenGroups.Any() )
        throw new ArgumentException( "At least one child group is required.", "childrenGroups" );

      if( containerCreator == null )
        throw new ArgumentNullException( "containerCreator" );

      if( statFunctionComparer == null )
        throw new ArgumentNullException( "statFunctionComparer" );

      m_childrenGroups = childrenGroups.ToList();
      m_readOnlyChildrenGroups = new ReadOnlyCollection<ListDataItemIndexGroup>( m_childrenGroups );
      m_containerCreator = containerCreator;
    }

    #endregion

    #region ChildrenGroups Property

    internal IList<ListDataItemIndexGroup> ChildrenGroups
    {
      get
      {
        return m_readOnlyChildrenGroups;
      }
    }

    private ReadOnlyCollection<ListDataItemIndexGroup> m_readOnlyChildrenGroups;
    private List<ListDataItemIndexGroup> m_childrenGroups;

    #endregion

    #region ChildrenCount Property

    private int ChildrenCount
    {
      get
      {
        return m_childrenGroups.Count;
      }
    }

    #endregion

    internal override void SlideLeft()
    {
      base.SlideLeft();

      foreach( ListDataItemIndexGroup group in m_childrenGroups )
      {
        group.Container.SlideLeft();
      }
    }

    internal override void SlideRight()
    {
      base.SlideRight();

      foreach( ListDataItemIndexGroup group in m_childrenGroups )
      {
        group.Container.SlideRight();
      }
    }

    internal override void Add( LinkedListNode<object> groupValue )
    {
      if( groupValue == null )
        throw new ArgumentNullException( "memberInfo" );

      int index;
      if( this.FindGroupIndex( groupValue.Value, false, false, out index ) )
      {
        m_childrenGroups[ index ].Container.Add( groupValue.Next );
      }
      else
      {
        // Determine the new group data item's index starting position.
        int indexStart;
        if( index < m_childrenGroups.Count )
        {
          indexStart = m_childrenGroups[ index ].Container.IndexStart;
        }
        else 
        {
          ListDataItemIndexNode lastContainer = m_childrenGroups.Last().Container;
          indexStart = lastContainer.IndexStart + lastContainer.Length;
        }

        // Create the whole branch that is containing the newly added item.
        ListDataItemIndexNode childContainer = m_containerCreator.Invoke( groupValue.Next, indexStart );
        ListDataItemIndexGroup childGroup = new ListDataItemIndexGroup( groupValue.Value, childContainer );

        m_childrenGroups.Insert( index, childGroup );
      }

      // We must count the newly added item has part of the current group
      // or one of its children.
      this.Grow();

      // Every child group that comes after the group that has the newly
      // added item must slide their starting index to the right.
      for( int i = index + 1; i < m_childrenGroups.Count; i++ )
      {
        m_childrenGroups[ i ].Container.SlideRight();
      }
    }

    internal override bool Remove( int index )
    {
      // Find out the index of the child container which contains
      // the removed item.
      int childIndex = 0;
      while( childIndex < m_childrenGroups.Count )
      {
        ListDataItemIndexNode childNode = m_childrenGroups[ childIndex ].Container;
        int indexInNode = index - childNode.IndexStart;

        if( ( indexInNode >= 0 ) && ( indexInNode < childNode.Length ) )
          break;

        childIndex++;
      }

      Debug.Assert( childIndex < m_childrenGroups.Count );

      int slideIndex;

      // An empty child container must be removed from the current group.
      if( m_childrenGroups[ childIndex ].Container.Remove( index ) )
      {
        m_childrenGroups.RemoveAt( childIndex );

        // The current group is now empty, it must be removed from the index.
        if( m_childrenGroups.Count <= 0 )
          return true;

        slideIndex = childIndex;
      }
      else
      {
        slideIndex = childIndex + 1;
      }

      // We must not count the newly removed item that was part of the current group
      // or one of its children.
      this.Shrink();

      // Every child group that comes after the group that had the newly
      // removed item must slide their starting index to the left.
      for( int i = slideIndex; i < m_childrenGroups.Count; i++ )
      {
        m_childrenGroups[ i ].Container.SlideLeft();
      }

      return false;
    }

    internal override int GetDataItemIndex(
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      // We are starting in this group.
      if( groupValue == null )
      {
        if( skipStartingItem )
          return ( reverseOrder )
                   ? this.IndexStart - 1
                   : this.IndexStart + this.Length;

        return ( reverseOrder )
                 ? this.IndexStart + this.Length - 1
                 : this.IndexStart;
      }

      // Find the group that match.
      int index;
      if( this.FindGroupIndex( groupValue.Value, reverseOrder, skipStartingItem, out index ) )
        return m_childrenGroups[ index ].Container.GetDataItemIndex( groupValue.Next, reverseOrder, skipStartingItem );

      // Get the closest item to the target.
      int dataItemIndex;
      if( index < m_childrenGroups.Count )
      {
        dataItemIndex = m_childrenGroups[ index ].Container.IndexStart;
      }
      // The target item is out of bound.
      else
      {
        dataItemIndex = this.IndexStart + this.Length;
      }

      if( reverseOrder )
      {
        dataItemIndex -= 1;
      }

      return dataItemIndex;
    }

    internal override bool GetGroupCount( LinkedListNode<int> groupCount )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      LinkedListNode<int> nextGroupCount = groupCount.Next;
      bool isCounted = ( nextGroupCount == null );

      if( !isCounted )
      {
        foreach( ListDataItemIndexGroup group in m_childrenGroups )
        {
          // We must count the current group if one of it's child group
          // is counted.
          if( group.Container.GetGroupCount( nextGroupCount ) )
          {
            groupCount.Value += 1;
            isCounted = true;
          }
        }
      }
      else
      {
        groupCount.Value += m_childrenGroups.Count;
      }

      return isCounted;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> groupValue )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      LinkedListNode<int> nextGroupCount = groupCount.Next;
      bool isCounted = ( nextGroupCount == null );

      if( !isCounted )
      {
        // All items on this level must be counted.
        if( groupValue == null )
          return this.GetGroupCount( groupCount );

        // Find the group that match or is the closest to the target value.
        int index;
        if( this.FindGroupIndex( groupValue.Value, false, false, out index ) )
        {
          // Check if one of the children of the current group is counted.
          isCounted = m_childrenGroups[ index ].Container.GetGroupCount( nextGroupCount, groupValue.Next );
        }
      }

      if( isCounted )
      {
        groupCount.Value += 1;
      }

      return isCounted;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      // All items on this level must be counted.
      if( groupValue == null )
        return this.GetGroupCount( groupCount );

      // Find the group that match or is the closest to the target value.
      int index;
      bool useNextValue = this.FindGroupIndex( groupValue.Value, reverseOrder, skipStartingItem, out index );

      LinkedListNode<int> nextGroupCount = groupCount.Next;
      bool isCounted = false;

      // Count the current group only if at least one of its child group is counted.
      if( nextGroupCount == null )
      {
        groupCount.Value += ( reverseOrder )
                              ? ( index + 1 )
                              : ( m_childrenGroups.Count - index );
        isCounted = true;
      }
      else
      {
        int step = ( reverseOrder ) ? -1 : 1;
        LinkedListNode<object> nextValue = ( useNextValue ) ? groupValue.Next : ( LinkedListNode<object> )null;

        for( int i = index; i >= 0 && i < m_childrenGroups.Count; i += step )
        {
          if( m_childrenGroups[ i ].Container.GetGroupCount( nextGroupCount, nextValue, reverseOrder, skipStartingItem ) )
          {
            groupCount.Value += 1;
            isCounted = true;
          }
          nextValue = null;
        }
      }

      return isCounted;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> filterGroupValue,
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      if( filterGroupValue == null )
        return this.GetGroupCount( groupCount, groupValue, reverseOrder, skipStartingItem );

      int index;
      bool found = this.FindGroupIndex( filterGroupValue.Value, reverseOrder, skipStartingItem, out index );

      // The group doesn't exists.
      if( !found )
        return false;

      LinkedListNode<object> nextGroupValue = ( groupValue != null ) ? groupValue.Next : null;

      if( groupValue != null )
      {
        int startIndex;
        this.FindGroupIndex( groupValue.Value, reverseOrder, false, out startIndex );

        // The starting group has no impact on the group count.
        if( ( ( !reverseOrder ) && ( startIndex > index ) )
          || ( ( reverseOrder ) && ( startIndex < index ) ) )
          return false;

        if( index != startIndex )
        {
          nextGroupValue = null;
          skipStartingItem = false;
        }
      }

      if( ( index >= 0 ) && ( index < m_childrenGroups.Count ) )
      {
        if( m_childrenGroups[ index ].Container.GetGroupCount(
              groupCount.Next,
              nextGroupValue,
              reverseOrder,
              skipStartingItem ) )
        {
          groupCount.Value += 1;

          return true;
        }
      }

      return false;
    }

    internal override void ClearStatResults()
    {
      foreach( ListDataItemIndexGroup childGroup in this.ChildrenGroups )
      {
        childGroup.Container.ClearStatResults();
      }

      base.ClearStatResults();
    }

    internal override StatResult GetStatValue(
      StatFunction statFunction,
      LinkedListNode<object> groupValue )
    {
      if( groupValue != null )
      {
        int index;
        if( !this.FindGroupIndex( groupValue.Value, false, false, out index ) )
          return null;

        ListDataItemIndexNode childNode = this.ChildrenGroups[ index ].Container;

        return childNode.GetStatValue( statFunction, groupValue.Next );
      }
      else
      {
        return base.GetStatValue( statFunction, groupValue );
      }
    }

    internal override void SetStatValue(
      StatFunction statFunction,
      LinkedListNode<object> groupValue,
      StatResult statResult )
    {
      if( groupValue != null )
      {
        int index;
        if( !this.FindGroupIndex( groupValue.Value, false, false, out index ) )
          Debug.Assert( false );

        ListDataItemIndexNode childNode = this.ChildrenGroups[ index ].Container;

        childNode.SetStatValue( statFunction, groupValue.Next, statResult );
      }
      else
      {
        base.SetStatValue( statFunction, groupValue, statResult );
      }
    }

    private bool FindGroupIndex(
      object groupValue,
      bool reverseOrder,
      bool skipStartingItem,
      out int index )
    {
      // Find the item that match or is the closest to the target value.
      ListDataItemIndexGroup targetGroup = new ListDataItemIndexGroup( groupValue, null );
      IComparer<ListDataItemIndexGroup> comparer = new GroupComparer( this.ItemComparer );
      index = m_childrenGroups.BinarySearch( targetGroup, comparer );

      // The item was found.
      if( index >= 0 )
        return true;

      // Find out the position of the group if it would have exist.
      index = ~index;

      return false;
    }

    #region Private Fields

    private Func<LinkedListNode<object>, int, ListDataItemIndexNode> m_containerCreator;

    #endregion

    #region Private GroupComparer Nested Type

    private class GroupComparer : IComparer<ListDataItemIndexGroup>
    {
      internal GroupComparer( IComparer comparer )
      {
        if( comparer == null )
          throw new ArgumentNullException( "comparer" );

        m_comparer = comparer;
      }

      public int Compare( ListDataItemIndexGroup x, ListDataItemIndexGroup y )
      {
        object firstValue = x.Value;
        object secondValue = y.Value;

        return m_comparer.Compare( firstValue, secondValue );
      }

      private IComparer m_comparer;
    }

    #endregion
  }
}
